#region global preview camera
	globalvar D3D_GLOBAL_PREVIEW;
	globalvar D3D_LIGHT_MAX;     D3D_LIGHT_MAX = 16;
	
	function set3DGlobalPreview() {
		var d3_camera = new __3dCamera();
		d3_camera.setViewSize(2, 2);
		d3_camera.setFocusAngle(135, 45, 8);
		d3_camera.position.set(d3d_PolarToCart(0, 0, 0, d3_camera.focus_angle_x, d3_camera.focus_angle_y, d3_camera.focus_dist));
		
		d3_camera.projection = CAMERA_PROJECTION.orthograph;
		d3_camera.setMatrix();
		
		D3D_GLOBAL_PREVIEW = new __3dScene(d3_camera, "Global node preview");
		D3D_GLOBAL_PREVIEW.apply_transform = true;
		D3D_GLOBAL_PREVIEW.defer_normal    = false;
	
		var d3_scene_light0 = new __3dLightDirectional();
		d3_scene_light0.transform.position.set(-1, -2, 3);
		d3_scene_light0.transform.applyMatrix();
		d3_scene_light0.color  = $AAAAAA;
		
		var d3_scene_light1 = new __3dLightDirectional();
		d3_scene_light1.transform.position.set(1, 2, 3);
		d3_scene_light1.transform.applyMatrix();
		d3_scene_light1.color  = $FFFFFF;
	
		D3D_GLOBAL_PREVIEW.lightAmbient = $404040;
		D3D_GLOBAL_PREVIEW.addLightDirectional(d3_scene_light0);
		D3D_GLOBAL_PREVIEW.addLightDirectional(d3_scene_light1);
	}
#endregion

#macro D3DSCENE_PRESUBMIT  if(!is_struct(o)) return; matrix_stack_clear(); if(apply_transform) custom_transform.submitMatrix(); 
#macro D3DSCENE_POSTSUBMIT if(apply_transform) custom_transform.clearMatrix(); 

function __3dScene(_camera, _name = "New scene") constructor {
	camera = _camera;
	name   = _name;
	
	apply_transform     = false;
	custom_transform    = new __transform();
	
	lightAmbient		= cola(c_black, 1);
	lightDir_max		= 16;
	lightDir_shadow_max = 4;
	lightPnt_max		= 16;
	lightPnt_shadow_max = 4;
	
	cull_mode           = cull_noculling;
	blend               = BLEND.normal;
	enviroment_map      = noone;
	gammaCorrection     = true;
	
	defer_normal        = true;
	defer_normal_radius = 0;
	
	draw_background     = false;
	show_normal         = false;
	show_wireframe      = false;
	wireframe_width     = 1;
	wireframe_color     = cola(c_black, 1);
	wireframe_aa        = true;
	wireframe_shade     = false;
	wireframe_only      = false;
	
	ssao_enabled        = false;
	ssao_sample         = 32;
	ssao_radius         = 0.1;
	ssao_bias           = 0.1;
	ssao_strength       = 1.;
	ssao_blur_radius    = 9;
	
	backface_blending   = ca_white;
	
	enable_ztest        = true;
	deferData           = noone;
	
	static reset = function() {
		lightDir_count        = 0;
		lightDir_direction    = []; _lightDir_direction    = array_create( D3D_LIGHT_MAX * 4 ); 
		lightDir_color        = []; _lightDir_color        = array_create( D3D_LIGHT_MAX * 4 ); 
		lightDir_intensity    = []; _lightDir_intensity    = array_create( D3D_LIGHT_MAX * 1 ); 
		
		lightDir_shadow_count = 0;
		lightDir_shadow       = [];
		lightDir_shadowMap    = [];
		lightDir_viewMat      = [];
		lightDir_projMat      = [];
		lightDir_shadowBias   = [];
		
		lightPnt_count        = 0;
		lightPnt_position     = []; _lightPnt_position     = array_create( D3D_LIGHT_MAX * 4 ); 
		lightPnt_color        = []; _lightPnt_color        = array_create( D3D_LIGHT_MAX * 4 ); 
		lightPnt_intensity    = []; _lightPnt_intensity    = array_create( D3D_LIGHT_MAX * 1 ); 
		lightPnt_radius       = []; _lightPnt_radius       = array_create( D3D_LIGHT_MAX * 1 ); 
		
		lightPnt_shadow_count = 0;
		lightPnt_shadow       = [];
		lightPnt_shadowMap    = [];
		lightPnt_viewMat      = [];
		lightPnt_projMat      = [];
		lightPnt_shadowBias   = [];
		
	} reset();
	
	////- Submit
	
	static submit       = function(o, sh=noone) /*=>*/ { D3DSCENE_PRESUBMIT o.submit(      self, sh); D3DSCENE_POSTSUBMIT }
	static submitSel    = function(o, sh=noone) /*=>*/ { D3DSCENE_PRESUBMIT o.submitSel(   self, sh); D3DSCENE_POSTSUBMIT }
	static submitShader = function(o, sh=noone) /*=>*/ { D3DSCENE_PRESUBMIT o.submitShader(self, sh); D3DSCENE_POSTSUBMIT }
	
	////- Rendering
	
	static deferPass = function(object, w, h, _deferData = noone) {
		if(_deferData == noone) {
			_deferData = {
				geometry_data: array_create(4, noone),
				ssao:       noone,
				ssaoTemp:   noone,
				normalBlur: noone,
			}
		}
		
		geometryPass(_deferData, object, w, h);
		if(ssao_enabled) ssaoPass(_deferData);
		
		return _deferData;
	}
	
	static renderBackground = function(surf) {
		surface_set_shader(surf, sh_d3d_background);
			shader_set_color("light_ambient",	lightAmbient);
			shader_set_f("cameraPosition",		camera.position.toArray());
			shader_set_i("env_use_mapping",		is_surface(enviroment_map) );
			shader_set_surface("env_map",		enviroment_map );
			shader_set_dim("env_map_dimension",	enviroment_map );
				
			camera.setMatrix();
			camera.applyCamera();
				
			gpu_set_cullmode(cull_noculling);
			var _s = (camera.view_near + camera.view_far) / 2;
			
			matrix_set(matrix_world, matrix_build(camera.position.x, camera.position.y, camera.position.z, 0, 0, 0, _s, _s, _s));
			vertex_submit(global.SKY_SPHERE.VB[0], pr_trianglelist, -1);
			matrix_set(matrix_world, matrix_build_identity());
		surface_reset_shader();
		
		return surf;
	}
	
	static geometryPass = function(_deferData, object, w = 512, h = 512) {
		for( var i = 0; i < 4; i++ ) {
			_deferData.geometry_data[i] = surface_verify(_deferData.geometry_data[i], w, h, surface_rgba32float);
			surface_set_target_ext(i, _deferData.geometry_data[i]);
		}
			
		gpu_set_zwriteenable(true);
		
		gpu_set_ztestenable(true);
		gpu_set_alphatestenable(true);
		
		DRAW_CLEAR
		camera.setMatrix();
		camera.applyCamera();
		
		setRendering();
		
		shader_set(sh_d3d_geometry);
		shader_set_f("planeNear", camera.view_near);
		shader_set_f("planeFar",  camera.view_far);
		shader_set_i("use_8bit",  OS == os_macosx);
		shader_reset();
		
		submit(object, sh_d3d_geometry);
		
		resetRendering();
		surface_reset_target();
		
		if(defer_normal_radius && is_struct(deferData)) { 
			var _geos = deferData.geometry_data[2];
			var _sw = surface_get_width_safe(_geos);
			var _sh = surface_get_height_safe(_geos);
			deferData.normalBlur = surface_verify(deferData.normalBlur, _sw, _sh, surface_rgba32float);
			
			surface_set_shader(deferData.normalBlur, sh_d3d_normal_blur);
				shader_set_f("radius",      defer_normal_radius);
				shader_set_i("use_8bit",    OS == os_macosx);
				shader_set_dim("dimension", deferData.geometry_data[2]);
				
				draw_surface(deferData.geometry_data[2], 0, 0);
			surface_reset_shader();
			
			var _temp = deferData.geometry_data[2];
			deferData.geometry_data[2] = deferData.normalBlur;
			deferData.normalBlur       = _temp;
		}
	}
	
	static ssaoPass = function(_deferData) {
		var _sw = surface_get_width_safe(_deferData.geometry_data[0]);
		var _sh = surface_get_height_safe(_deferData.geometry_data[0]);
		_deferData.ssaoTemp = surface_verify(_deferData.ssaoTemp, _sw, _sh);
		
		surface_set_shader(_deferData.ssaoTemp, sh_d3d_ssao);
			shader_set_s("vPosition",       _deferData.geometry_data[0]);
			shader_set_s("vNormal",         _deferData.geometry_data[2]);
			shader_set_f("radius",          ssao_radius);
			shader_set_f("bias",            ssao_bias);
			shader_set_f("strength",        ssao_strength * 2);
			shader_set_f("projMatrix",      camera.getCombinedMatrix());
			shader_set_f("cameraPosition",  camera.position.toArray());
			
			draw_sprite_stretched(s_fx_pixel, 0, 0, 0, _sw, _sh);
		surface_reset_shader();
		
		_deferData.ssao = surface_verify(_deferData.ssao, _sw, _sh);
		
		surface_set_shader(_deferData.ssao, sh_d3d_ssao_blur);
			shader_set_f("dimension", _sw, _sh);
			shader_set_f("radius",    ssao_blur_radius);
			shader_set_s("vNormal",   _deferData.geometry_data[2]);
			
			draw_surface(_deferData.ssaoTemp, 0, 0);
		surface_reset_shader();
	}
	
	static apply = function(_deferData = noone, _shader = sh_d3d_default) {
		deferData = _deferData;
		
		shader_set(_shader);
		shader_set_i("use_8bit",  OS == os_macosx);
			
			#region ---- background ----
				shader_set_color("light_ambient",	lightAmbient);
				shader_set_i("env_use_mapping",		is_surface(enviroment_map) );
				shader_set_surface("env_map",		enviroment_map, false, true );
				shader_set_dim("env_map_dimension",	enviroment_map );
			#endregion
			
			shader_set_i("light_dir_count", lightDir_count);
			if(lightDir_count) {
				shader_set_f("light_dir_direction", 	lightDir_direction);
				shader_set_f("light_dir_color",			lightDir_color);
				shader_set_f("light_dir_intensity", 	lightDir_intensity);
				shader_set_i("light_dir_shadow_active", lightDir_shadow);
				
				for( var i = 0, n = array_length(lightDir_shadowMap); i < n; i++ )
					var _sid = shader_set_surface($"light_dir_shadowmap_{i}", lightDir_shadowMap[i], true);
				
				shader_set_f("light_dir_view",			lightDir_viewMat);
				shader_set_f("light_dir_proj",			lightDir_projMat);
				shader_set_f("light_dir_shadow_bias",	lightDir_shadowBias);
			}
			
			shader_set_i("light_pnt_count", lightPnt_count);
			if(lightPnt_count) {
				shader_set_f("light_pnt_position",  	lightPnt_position);
				shader_set_f("light_pnt_color",			lightPnt_color);
				shader_set_f("light_pnt_intensity", 	lightPnt_intensity);
				shader_set_f("light_pnt_radius",    	lightPnt_radius);
				
				shader_set_i("light_pnt_shadow_active", lightPnt_shadow);
				
				for( var i = 0, n = array_length(lightPnt_shadowMap); i < n; i++ )
					var _sid = shader_set_surface($"light_pnt_shadowmap_{i}", lightPnt_shadowMap[i], true, false);
				
				shader_set_f("light_pnt_view",			lightPnt_viewMat);
				shader_set_f("light_pnt_proj",			lightPnt_projMat);
				shader_set_f("light_pnt_shadow_bias",	lightPnt_shadowBias);
			}
			
			if(OS == os_windows && defer_normal && _deferData != noone && array_length(_deferData.geometry_data) > 2) {
				shader_set_i("mat_defer_normal", 1);
				shader_set_surface("mat_normal_map", _deferData.geometry_data[2]);
			} else 
				shader_set_i("mat_defer_normal", 0);
			
			#region ---- camera ----
				shader_set_f("cameraPosition",	camera.position.toArray());
				shader_set_i("gammaCorrection",	gammaCorrection);
				shader_set_f("planeNear",		camera.view_near);
				shader_set_f("planeFar",		camera.view_far );
				
				shader_set_f("viewProjMat",		camera.getCombinedMatrix() );
			#endregion
			
			#region ---- wireframe ----
				shader_set_i("show_wireframe",      show_wireframe);
				shader_set_i("wireframe_aa",        wireframe_aa);
				shader_set_i("wireframe_shade",     wireframe_shade);
				shader_set_i("wireframe_only",      wireframe_only);
				shader_set_f("wireframe_width",     max(0, wireframe_width));
				shader_set_color("wireframe_color", wireframe_color);
			#endregion
			
			shader_set_color("backface_blending", backface_blending);
			
		shader_reset();
	}
	
	static reApply = function(_shader) { apply(noone, _shader); }
	
	static setRendering = function() {
		switch(blend) {
			case BLEND.normal : gpu_set_ztestenable(1);           break;
			
			case BLEND.add    : 
				gpu_set_ztestenable(0);
				BLEND_ADD
				break;	
		}
		
		gpu_set_cullmode(cull_mode); 
	}
	
	static resetRendering = function() {
		BLEND_NORMAL
		gpu_set_cullmode(cull_noculling);
		gpu_set_ztestenable(false);
		gpu_set_alphatestenable(false);
	}
	
	////- Data
	
	static addLightDirectional = function(light) {
		if(lightDir_count >= lightDir_max) { noti_warning("Direction light limit exceeded"); return self; }
		
		light.transform.submitMatrix(); 
		var _mat = matrix_stack_top();
		var _pos = [ light.transform.position.x, light.transform.position.y, light.transform.position.z, 1 ];
		var _pos = matrix_multiply_vector_column(_mat, _pos); 
		light.transform.clearMatrix();
		
		array_append(lightDir_direction, [_pos[0], _pos[1], _pos[2]]);
		array_append(lightDir_color,     colToVec4(light.color));
		
		array_push(lightDir_intensity, light.intensity);
		array_push(lightDir_shadow, light.shadow_active);
		
		if(light.shadow_active) {
			if(lightDir_shadow_count < lightDir_shadow_max) {
				array_push(lightDir_shadowMap, light.shadow_map);
				lightDir_shadow_count++;
			} else 
				noti_warning("Direction light shadow caster limit exceeded");
		}
		array_append(lightDir_viewMat, light.shadow_map_view);
		array_append(lightDir_projMat, light.shadow_map_proj);
		array_push(lightDir_shadowBias, light.shadow_bias);
		lightDir_count++;
		
		return self;
	}
	
	static addLightPoint = function(light) {
		if(lightPnt_count >= lightPnt_max) { noti_warning("Point light limit exceeded"); return self; }
		
		light.transform.submitMatrix(); 
		var _mat = matrix_stack_top();
		var _pos = [ light.transform.position.x, light.transform.position.y, light.transform.position.z, 1 ];
		var _pos = matrix_multiply_vector_column(_mat, _pos); 
		light.transform.clearMatrix();
		
		array_append(lightPnt_position,  [_pos[0], _pos[1], _pos[2]]);
		array_append(lightPnt_color,     colToVec4(light.color));
		
		array_push(lightPnt_intensity, light.intensity);
		array_push(lightPnt_radius,    light.radius);
		array_push(lightPnt_shadow,    light.shadow_active);
		
		if(light.shadow_active) {
			if(lightPnt_shadow_count < lightPnt_shadow_max) {
				array_push(lightPnt_shadowMap, light.shadow_map);
				lightPnt_shadow_count++;
			} else 
				noti_warning("Point light shadow caster limit exceeded");
		}
		array_append(lightPnt_viewMat, light.shadow_map_view);
		array_append(lightPnt_projMat, light.shadow_map_proj);
		array_push(lightPnt_shadowBias, light.shadow_bias);
		lightPnt_count++;
		
		return self;
	}
	
	static fixArray = function() {
		for( var i = 0; i < min(lightDir_count, D3D_LIGHT_MAX); i++ ) {
			_lightDir_direction[i*4+0] = lightDir_direction[i*3+0];
			_lightDir_direction[i*4+1] = lightDir_direction[i*3+1];
			_lightDir_direction[i*4+2] = lightDir_direction[i*3+2];
			_lightDir_direction[i*4+3] = 0;
		}
		
		for( var i = 0, n = array_length(lightDir_color);     i < n; i++ ) _lightDir_color[i]     = lightDir_color[i];
		for( var i = 0, n = array_length(lightDir_intensity); i < n; i++ ) _lightDir_intensity[i] = lightDir_intensity[i];
		
		for( var i = 0; i < min(lightPnt_count, D3D_LIGHT_MAX); i++ ) {
			_lightPnt_position[i*4+0] = lightPnt_position[i*3+0];
			_lightPnt_position[i*4+1] = lightPnt_position[i*3+1];
			_lightPnt_position[i*4+2] = lightPnt_position[i*3+2];
			_lightPnt_position[i*4+3] = 0;
		}
		
		for( var i = 0, n = array_length(lightPnt_color);     i < n; i++ ) _lightPnt_color[i]     = lightPnt_color[i];
		for( var i = 0, n = array_length(lightPnt_intensity); i < n; i++ ) _lightPnt_intensity[i] = lightPnt_intensity[i];
		for( var i = 0, n = array_length(lightPnt_radius);    i < n; i++ ) _lightPnt_radius[i]    = lightPnt_radius[i];
	}
		
	static toString = function() { return $"[3D Scene] {name}"; }
}